package YAPserver;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.SocketException;

import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LayoutStyle;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

import YAPenvironment.YAPconstant;
import YAPenvironment.YAPconstant.Command;

/**
 * Form for the server with action- and keyListener.
 * A simple GUI with a jTextField for entering the port the server should run on.
 * <p>
 * A button to start the server.
 * <p>
 * A jTextArea to redirect the system.out to the GUI
 * and a jTextfField to interact with while the server is running.
 * @author DMiagi
 *
 */
public class FormServer extends JFrame implements ActionListener, KeyListener, MouseListener{

	private static final long serialVersionUID = 1909333536097912419L;
	
	private MaxConnection maxConnection;
	private JButton setMaxConnection;
	private JMenuItem disconnectAll;
	private JMenuItem close;
    private JButton create;
    private JTextField input;
    private JMenu jMenu;
    public JMenuBar jMenuBar;
    private JSplitPane jSplitPane;
    private JLabel labelPort;
    private JTextField portTF;
    private JButton send;
    private JTextArea output;
    
    private JLabel labelLeft;
    private JScrollPane jspClients;
    private DefaultListModel<ClientHandler> clientsModel;
    private JList<ClientHandler> listClients;
    private JLabel leftTitle;
    
    JPopupMenu popupMenu;
    JMenuItem kick;
    JMenuItem whisper;
    
    
	private static Thread t;
	
	/**
	 * Creates the GUI
	 */
	public FormServer(){
		
				createGUI();
			
	}
	
	private void createGUI(){
		
		setLookAndFeel();
	      
		initialize();
		
		addComponents();

        setTitle("Server");
		setIconImage(YAPconstant.ICONIMAGE);
		setLocationRelativeTo(null);
		
		
		setResizable(true);
		
		if( !YAPconstant.C_DEBUG )
			redirectSystemStreams();
	
		addWindowListener(new ExitListener());
		setVisible(true);
	}
	
	private void setLookAndFeel(){
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch(Exception e){
			System.out.println("Couldn't set Look-and-Feel");
		}
	}
	
	private void initialize(){
			
		labelPort = new JLabel();
	    portTF = new JTextField();
	    create = new JButton();
	    
	    labelLeft = new JLabel();
	    leftTitle = new JLabel();
	    clientsModel = new DefaultListModel<ClientHandler>();
	    listClients = new JList(clientsModel);
	    listClients.addMouseListener(this);
	    jspClients = new JScrollPane(listClients);
	        
        output = new JTextArea();
        output.setEditable(false);
        
        jSplitPane = new JSplitPane();
        input = new JTextField();
        send = new JButton();
        jMenuBar = new JMenuBar();
        jMenu = new JMenu();
        maxConnection = new MaxConnection();
        close = new JMenuItem();
        disconnectAll = new JMenuItem();
        
        
        popupMenu = new JPopupMenu();
        popupMenu.add(whisper = new JMenuItem());
		popupMenu.add(kick= new JMenuItem());
	}
	
	private void addComponents(){

		labelPort.setText("PORT:");

		portTF.addActionListener( this );
		portTF.setText(YAPserver.C_PORT+"");
		
        create.setText("start Server");
        create.addActionListener(this);

        input.addKeyListener(this);
        
        send.setText("send");
		send.addActionListener(this);
        
        jMenu.setText("Server");

        close.setText("shut down");
        close.addActionListener(this);
        
		disconnectAll.setText("disconnect all Players");
		disconnectAll.addActionListener(this);
        
		leftTitle.setText("Clients:");
		
		output.append("Console:\n");
		
		kick.setText("kick");
		kick.addActionListener(this);
		whisper.setText("whisper");
		whisper.addActionListener(this);
		
		arrangeComponents();
		
        addMenuBar();
        
        pack();
	}
	
	private void arrangeComponents(){
		
		arrangeLeftComponents(jSplitPane);
		
		arrangeRightComponents(jSplitPane);
		
		 send.setHorizontalAlignment(SwingConstants.RIGHT);
		 GroupLayout layout = new GroupLayout(getContentPane());
		    getContentPane().setLayout(layout);
		    layout.setHorizontalGroup(
		        layout.createParallelGroup(GroupLayout.Alignment.LEADING)
		        .addGroup(layout.createSequentialGroup()
		            .addGap(12, 12, 12)
		            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
		                .addGroup(layout.createSequentialGroup()
		                    .addComponent(labelPort, GroupLayout.PREFERRED_SIZE, 46, GroupLayout.PREFERRED_SIZE)
		                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
		                    .addComponent(portTF, GroupLayout.PREFERRED_SIZE, 62, GroupLayout.PREFERRED_SIZE)
		                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
		                    .addComponent(create, GroupLayout.DEFAULT_SIZE, 218, Short.MAX_VALUE)
		                    .addGap(330, 330, 330))
		                .addComponent(jSplitPane)
		                .addGroup(layout.createSequentialGroup()
		                    .addComponent(input)
		                    .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
		                    .addComponent(send, GroupLayout.PREFERRED_SIZE, 86, GroupLayout.PREFERRED_SIZE)))
		            .addContainerGap())
		    );
		    layout.setVerticalGroup(
		        layout.createParallelGroup(GroupLayout.Alignment.LEADING)
		        .addGroup(layout.createSequentialGroup()
		            .addGap(11, 11, 11)
		            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
		                .addComponent(labelPort)
		                .addComponent(portTF, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
		                .addComponent(create))
		            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
		            .addComponent(jSplitPane, GroupLayout.DEFAULT_SIZE, 328, Short.MAX_VALUE)
		            .addGap(6, 6, 6)
		            .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
		                .addComponent(input, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
		                .addComponent(send))
		            .addGap(11, 11, 11))
		    );
	}
	
	private void arrangeLeftComponents(JSplitPane jSplitPane){
		labelLeft.setLayout(new GridBagLayout());
		java.awt.GridBagConstraints c;

		leftTitle.setBackground(this.getBackground());
		leftTitle.setOpaque(true);
        c = new java.awt.GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.ipadx = 366;
        c.insets = new Insets(0,10,0,0);
        c.anchor = java.awt.GridBagConstraints.NORTHWEST;
        labelLeft.add(leftTitle, c);

        c = new java.awt.GridBagConstraints();
        c.gridx = 0;
        c.gridy = 1;
        c.fill = java.awt.GridBagConstraints.BOTH;
        c.ipadx = 377;
        c.ipady = 257;
        c.anchor = java.awt.GridBagConstraints.NORTHWEST;
        c.weightx = 1.0;
        c.weighty = 1.0;
        c.insets = new java.awt.Insets(6, 0, 0, 0);
        labelLeft.add(jspClients, c);

        labelLeft.setPreferredSize(new Dimension(100,0));
        pack();
		
		jSplitPane.setLeftComponent(labelLeft);
	}
	
	private void arrangeRightComponents(JSplitPane jSplitPane){
		
		jSplitPane.setRightComponent(output);
		
	}
	
	private void addMenuBar(){
		
		jMenu.add(maxConnection);
		jMenu.add(close);
        jMenu.add(disconnectAll);
        jMenu.setMinimumSize(maxConnection.getMinimumSize());
        jMenuBar.add(jMenu);
        setJMenuBar(jMenuBar);
	}

	private void updateTextArea(final String text) {
		  SwingUtilities.invokeLater(new Runnable() {
		    public void run() {
		      output.append(text);
		    }
		  });
	}
		 
	public void redirectSystemStreams() {
	  OutputStream out = new OutputStream() {
	    @Override
	    public void write(int b) throws IOException {
	      updateTextArea(String.valueOf((char) b));
	    }
	 
	    @Override
	    public void write(byte[] b, int off, int len) throws IOException {
	      updateTextArea(new String(b, off, len));
	    }
	 
	    @Override
	    public void write(byte[] b) throws IOException {
	      write(b, 0, b.length);
	    }
	  };
	 
	  System.setOut(new PrintStream(out, true));
	  System.setErr(new PrintStream(out, true));
	}

	/**
	 * Interaction with the GUI
	 * <p>
	 * Either starting the server, shutting it down, or interacting with it over the "console".
	 */
	@Override
	public void actionPerformed(ActionEvent e){
		if(e.getSource() == create){
			if(create.getText() != null && create.getText().equals("start Server")){
				try{
					YAPserver.serverSocket = new ServerSocket(Integer.parseInt(portTF.getText()));
					YAPserver.C_PORT=Integer.parseInt(portTF.getText());
					YAPserver.udpSocket = new DatagramSocket(YAPconstant.C_MULTICASTPORT2+1);
					YAPserver.started =true;
					t = new Thread(new YAPserver());					
					t.start();
					
					new DiscoveryServer();
					
					create.setText("stop Server");
				}catch(Exception exc){
					if(YAPconstant.C_DEBUG){ exc.printStackTrace(); }
					System.out.println("Server could not be started. Please check whether port number is valid");
				}
			}else{
				killServer();
			}
		}
		else if(e.getSource() == send){
			consoleEvent();
		}
		else if(e.getSource() == close){
			killServer();
		}
		else if(e.getSource() == disconnectAll){
			disconnectAll();
		}
		else if(e.getSource() == kick){
			if( listClients.getSelectedValue() !=null ){
				try{
					listClients.getSelectedValue().writeMessage(Command.kick.toString()+YAPconstant.C_SEPARATOR+"");
				}catch (Exception exc){}
					listClients.getSelectedValue().disconnect();
			}
		}
		else if(e.getSource() == whisper){
			if( listClients.getSelectedValue() !=null ){
				input.setText("/"+listClients.getSelectedValue().toString()+" ");
				input.grabFocus();
			}
		}
	}
	
	private void disconnectAll(){
		for(ClientHandler cH: YAPserver.connectedClients){
			try{
				cH.writeMessage(Command.kick.toString()+YAPconstant.C_SEPARATOR+"");
			}catch (Exception e){}
			cH.disconnect();
		}
	}
	
	/**
	 * Empty Method
	 */
	@Override
	public void keyTyped(KeyEvent e) {
	}
	/**
	 * Empty Method
	 */
	@Override
	public void keyPressed(KeyEvent e) {
	}
	/**
	 * If Enter is released, the "consoleEvent" is started:
	 * <p>
	 * shows either information from the server, such as which players are on the server (most likely not used anymore)
	 * or which lobbies are open
	 * or it sends a Message to every client connected to the server
	 */
	@Override
	public void keyReleased(KeyEvent e) {
		if(e.getKeyCode()==KeyEvent.VK_ENTER){
			consoleEvent();
		}
	}
	
	/**
	 * Userinput will be handled in this method.
	 */
	private void consoleEvent(){
		if(!YAPserver.started){
			output.append("Server hasn't been started yet\n");
			output.append("Chose a Port between 1024 � 61152 and press \"startServer\"");
			return;
		}
		if(input.getText().equalsIgnoreCase("games")){
			System.out.println(PlayedGames.getPlayedGames());
		}else if(input.getText().equalsIgnoreCase("players")){
			System.out.println("connected clients:");
			for(ClientHandler cH: YAPserver.connectedClients){
				System.out.print(cH.getName()+", ");
			}
			System.out.println();
		}else if(input.getText().equalsIgnoreCase("lobbies") || input.getText().equalsIgnoreCase("lobby")){
			System.out.println("Lobbies:");
			for(String lobby: YAPserver.lobbyList.keySet()){
				System.out.print(lobby+", ");
			}
			System.out.println();
		}else if(input.getText().startsWith("/")){
			for(ClientHandler cH: YAPserver.connectedClients){
				if(input.getText().split(" ")[0].substring(1).equals(cH.toString())){
					try{
						cH.writeMessage(Command.sendMsgFrom+YAPconstant.C_SEPARATOR+"0"+YAPconstant.C_SEPARATOR+"SERVER:"+YAPconstant.C_SEPARATOR+input.getText().substring(1+cH.toString().length()));
						input.setText(input.getText().split(" ")[0]+" ");
					}catch(Exception e){};
				}
			}
		}else{
			sendMessage(input.getText());
			input.setText("");
		}
	}
	/**
	 * Sends a message to all clients connected to the server.
	 * @param text
	 */
	private void sendMessage(String text) {
		System.out.println(text);
		//text=text.replaceAll(".", YAPconstant.separator);
		for(ClientHandler cH: YAPserver.connectedClients){
			try {
				cH.writeMessage(Command.sendMsg.toString()+YAPconstant.C_SEPARATOR+text);
			} catch (SocketException e) {
				//ignore
			} catch (Exception e){
				if(YAPconstant.C_DEBUG){ e.printStackTrace(); }
				System.out.println("Culdnt send: \""+Command.sendMsg.toString()+YAPconstant.C_SEPARATOR+text+"\" to "+cH.getName());
			}
		}
		
	}
	
	class ExitListener extends WindowAdapter{
		public void windowClosing(WindowEvent event){
			killServer();
		}
	}
	
	private class MaxConnection extends JMenuItem implements ActionListener {
		private JTextField input;
		public MaxConnection() {
			input = new JTextField();
			input.setText(""+YAPserver.maxConnection);
			setMaxConnection = new JButton("set");
			setMaxConnection.addActionListener(this);
			setLayout(new BorderLayout());
				add(new JLabel("Max. players:  "),BorderLayout.WEST);
				add(input,BorderLayout.CENTER);
				add(setMaxConnection, BorderLayout.EAST);
			setMinimumSize(new Dimension(40,10));
		}
		@Override
		public void actionPerformed(ActionEvent e) {
			try{
				int i =Integer.parseInt( input.getText() );
				YAPserver.maxConnection=i;
				System.out.println("Maximum number of players was set to "+i+
						"\nIf there are already more on the server, they won't be kicked. But no one new can connect.");
			}catch(NumberFormatException exc){
				System.out.println("Maximum of players must be a number!");
				input.setText(""+YAPserver.maxConnection);
				
			}
		}
	}
	
	/**
	 * Shuts down the server.
	 */
	public static void killServer(){
		System.out.println("Server shutting down");
		YAPserver.started = false;
		for(ClientHandler cH: YAPserver.connectedClients){
			try{
				cH.getSocket().close();
			}catch (Exception e){
				
			}
		}
		try{
			YAPserver.serverSocket.close();
			DiscoveryServer.close();
			
			YAPserver.udpSocket.close();
			
			t.interrupt();			
		}catch (Exception e){
		}
		System.exit(0);
	}
	
	public void add(ClientHandler cH){
		clientsModel.addElement(cH);
	}
	
	public void remove(ClientHandler cH){
		clientsModel.removeElement(cH);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		if(e.getClickCount()==2){
			input.setText("/"+listClients.getSelectedValue()+" ");
			input.grabFocus();
		}
		popup(e);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		popup(e);
	}		
	@Override
	public void mouseReleased(MouseEvent e) {
		popup(e);
	}
	@Override
	public void mouseEntered(MouseEvent e) {
	}
	@Override
	public void mouseExited(MouseEvent e) {
	}
	
	private void popup(MouseEvent e){
		listClients.setSelectedIndex( listClients.locationToIndex(e.getPoint()) );
	    if(listClients.getSelectedValue()!=null && e.isPopupTrigger()){
	    	popupMenu.show(listClients, e.getX(), e.getY());
	    }
	}
}
